Add support for custom target-specific runners
authorIngvar Stepanyan <me@rreverser.com>
Wed, 26 Apr 2017 00:16:17 +0000 (01:16 +0100)
committerIngvar Stepanyan <me@rreverser.com>
Fri, 12 May 2017 22:14:11 +0000 (23:14 +0100)
When `target.$triple.runner` is specified, it will be used for any execution commands by cargo including `cargo run`, `cargo test` and `cargo bench`. The original file is passed to the runner executable as a first argument.

This allows to run tests when cross-comping Rust projects.

This is not a complete solution, and might be extended in future for better ergonomics to support passing extra arguments to the runner itself or overriding runner from command line, but it should already unlock major existing usecases.

Fixes #1411
Resolves #3626

src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_rustc/compilation.rs
tests/tool-paths.rs

index 1256787f4f8aa1ea12bce637ffc9db00339a0860..26faf702e316e253871b54c3c6db2d8b2697f80e 100644 (file)
@@ -662,8 +662,11 @@ fn scrape_target_config(config: &Config, triple: &str)
         None => return Ok(ret),
     };
     for (lib_name, value) in table {
-        if lib_name == "ar" || lib_name == "linker" || lib_name == "rustflags" {
-            continue
+        match lib_name.as_str() {
+            "ar" | "linker" | "runner" | "rustflags" => {
+                continue
+            },
+            _ => {}
         }
 
         let mut output = BuildOutput {
index b34850a7978f5320feaf5c36d120dee6c195fc4f..594f3cb195c7a65dab9edfb4e7ba3d8edbd66d72 100644 (file)
@@ -4,7 +4,7 @@ use std::path::PathBuf;
 use semver::Version;
 
 use core::{PackageId, Package, Target, TargetKind};
-use util::{self, CargoResult, Config, ProcessBuilder, process, join_paths};
+use util::{self, CargoResult, Config, LazyCell, ProcessBuilder, process, join_paths};
 
 /// A structure returning the result of a compilation.
 pub struct Compilation<'cfg> {
@@ -53,6 +53,8 @@ pub struct Compilation<'cfg> {
     pub target: String,
 
     config: &'cfg Config,
+
+    target_runner: LazyCell<Option<PathBuf>>,
 }
 
 impl<'cfg> Compilation<'cfg> {
@@ -72,6 +74,7 @@ impl<'cfg> Compilation<'cfg> {
             cfgs: HashMap::new(),
             config: config,
             target: String::new(),
+            target_runner: LazyCell::new(),
         }
     }
 
@@ -91,10 +94,24 @@ impl<'cfg> Compilation<'cfg> {
         self.fill_env(process(cmd), pkg, true)
     }
 
+    fn target_runner(&self) -> CargoResult<&Option<PathBuf>> {
+        self.target_runner.get_or_try_init(|| {
+            let key = format!("target.{}.runner", self.target);
+            Ok(self.config.get_path(&key)?.map(|v| v.val))
+        })
+    }
+
     /// See `process`.
     pub fn target_process<T: AsRef<OsStr>>(&self, cmd: T, pkg: &Package)
                                            -> CargoResult<ProcessBuilder> {
-        self.fill_env(process(cmd), pkg, false)
+        let builder = if let &Some(ref runner) = self.target_runner()? {
+            let mut builder = process(runner);
+            builder.arg(cmd);
+            builder
+        } else {
+            process(cmd)
+        };
+        self.fill_env(builder, pkg, false)
     }
 
     /// Prepares a new process with an appropriate environment to run against
index dcb9e63681121feac4846578618c8225b57f26b8..641ccba9a74c0a4f360ddc5497bbd6584fc119af 100644 (file)
@@ -124,3 +124,48 @@ fn relative_tools() {
 [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
 ", url = foo_url, ar = output.0, linker = output.1)))
 }
+
+#[test]
+fn custom_runner() {
+    let target = rustc_host();
+
+    let foo = project("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.0.1"
+        "#)
+        .file("src/main.rs", "fn main() {}")
+        .file("tests/test.rs", "")
+        .file("benches/bench.rs", "")
+        .file(".cargo/config", &format!(r#"
+            [target.{}]
+            runner = "nonexistent-runner"
+        "#, target));
+
+    foo.build();
+
+    assert_that(foo.cargo("run").args(&["--", "--param"]),
+                execs().with_stderr_contains(&format!("\
+[COMPILING] foo v0.0.1 ({url})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `nonexistent-runner target[/]debug[/]foo[EXE] --param`
+", url = foo.url())));
+
+    assert_that(foo.cargo("test").args(&["--test", "test", "--verbose", "--", "--param"]),
+                execs().with_stderr_contains(&format!("\
+[COMPILING] foo v0.0.1 ({url})
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `nonexistent-runner [..][/]target[/]debug[/]deps[/]test-[..][EXE] --param`
+", url = foo.url())));
+
+    assert_that(foo.cargo("bench").args(&["--bench", "bench", "--verbose", "--", "--param"]),
+                execs().with_stderr_contains(&format!("\
+[COMPILING] foo v0.0.1 ({url})
+[RUNNING] `rustc [..]`
+[RUNNING] `rustc [..]`
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] `nonexistent-runner [..][/]target[/]release[/]deps[/]bench-[..][EXE] --param --bench`
+", url = foo.url())));
+}